Dividieren in Assembler

← Zurück zum Forum

Hallo! Hier eine kleine Routine, um in Assembler zu dividieren. Zunächst baue ich mal zur Veranschaulichung die Routine in Basic und setze sie dann in Assembler um. Ich nutze bewußt keine Fließkommazahlen in Assembler, da in den meisten Anwendungen die Genauigkeit ausreicht (z.B. in Spielen). Wenn man es genauer braucht, dann kann man ja entweder mit Rest arbeiten, oder halt den Weg über Fließkommazahlen gehen. Hier zunächst der Basic-Code: X = A/B

5 Graphics 0
10 Input A
20 Input B
30 A=A-B
40 If A<0 Then Goto 70
50 X=X+1
60 Goto 30
70 Print X

Das läßt sich nun leicht in Assembler umsetzen...

... und hier nun die Division in Assembler (8Bit Wertebereich 255-0):

var3 = var1/var2

            .proc div
    lp1     
            sec
            lda var1
            sbc var2
            sta var1
            bcc weiter
            inc var3
            jmp lp1
    weiter
            rts
            .endp

Wenn man var1 weiter verwenden will, kann man auch ohne Zwischenspeichern von var1 rechnen. Der eigentliche "Divisions-Loop" läuft ohne Zwischenspeichern schneller. (Samt Initialisierung der Division komme ich mit Variablen außerhalb der Zero Page auf 367 statt 942 cycles; z.B. braucht INX nur 2 cycles, INC 5-6; wenn man nicht damit rechnet, dass die Division meist ein Ergebnis von 1 oder 2 hat, dann rechnet es sich auch dann, wenn man X sichern muss).

Wenn man den Rest der Division haben will (var1 modulo var2) dann kann man bei beiden Verfahren am Ende var1 wieder addieren und hat den Rest dann im Akkumulator.

Beim Beispiel oben müsste man noch darauf achten, dass var3 initialisiert wird, weil die Subroutine sonst bei jedem Aufruf weiterzählt.

lp1     
            ldx #$ff
            sec
            lda var1
loop            
            inx
            sbc var2
            bcs loop
            stx var3    
weiter
            adc var2    ; optional
                    ; macht die letzte Subtraktion, mit der man unter 0 gekommen ist, wieder rückgängig 
                    ; und hinterlässt den Divisionsrest im Akkumulator

            rts
            .endp

(X wird auf #$FF initialisiert, damit es beim ersten Durchlaufen des Loops auf 0 gesetzt wird. Würde man das INX nach dem SBC setzen, dann würde es auch hochzählen, wenn man mit dem SBC unter 0 kommt und das Ergebnis wäre eins zu hoch.)

Fürs Cycle-Zählen habe ich übrigens den Online-Assembler auf https://www.masswerk.at/6502/assembler.html verwendet.

Das 1:1-Übertragen aus BASIC birgt mE das Risiko, dass man in Assembler mögliche Vereinfachungen (wie das Zählen mit X oder Y) leichter übersieht.

... lieben Dank für die Optimierung! Bestimmte Sachverhalte zunächst in Basic umzusetzen und dann 1:1 in Assembler umzusetzen macht dann Sinn, wenn man es einfach erklären möchte für diejenigen, die noch nicht so firm mit Assembler sind. Für mich selber nutze ich das inzwischen nur noch in den Fällen, wo ich einen hartnäckigen Denkfehler zu haben scheine. solch einen Fehler zu erkennen und zu eliminieren fällt mir dann ggf. leichter. Das geht aber nur für kleine Routinen. Danach optimiere ich es, wenn es zeitlich nötig ist. In diesem Fall sind die Optimierungen natürlich immer nötig, da man wenig CPU-Zeit für eine Division verbrauchen will. Ich werde dann diese optimierte Routine ins Atari-Wiki so übernehmen!

Liebe Grüße Peter

p.s. Toll, daß wir uns hier so gut austauschen und jeder sich hier einbringt!!!!